// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: jhkim

#include <experimental/filesystem>
#include <highfive/H5File.hpp>

#include "coin2/base/config/app_config.h"
#include "coin2/base/config/args.h"
#include "coin2/base/log.h"
#include "coin2/exchange/base/log/strategy_logger.h"
#include "coin2/exchange/base/order/utils.h"
#include "coin2/exchange/base/symbology/product_encyclopedia.h"
#include "presto/quant/math/moving_average.h"
#include "presto/quant/feature/feature.h"

#include "backward.hpp"

int64_t kUnitSizeNs = 60e9;


enum BalanceType {
  BALTYPE_BALANCE,
  BALTYPE_RESERVE,
  NUM_BALTYPE,
};


struct SymStat {
  typedef presto::math::TimeWindowMovingAverage<double> ma;

  // lhs fills -> last relpos -> rhs fills
  struct SignStats {
    double subpq = 0, subq = 0, fillpq = 0, fillq = 0;
    double lhs_makerq = 0, lhs_takerq = 0, lhs_makerpq = 0.0, lhs_takerpq = 0.0;
    double rhs_makerq = 0, rhs_takerq = 0, rhs_makerpq = 0.0, rhs_takerpq = 0.0;
    double req = 0, fill_subamt = 0;
  };

  bool is_inverse = false;
  double contract_value = 1;
  double maker_fee_rate = 0;
  double taker_fee_rate = 0;

  explicit SymStat(const coin2::exchange::base::symbology::IProductInfo* product_info) {
    if (product_info != nullptr) {
      maker_fee_rate = product_info->maker_fee_rate();
      taker_fee_rate = product_info->taker_fee_rate();
      is_inverse = product_info->is_inverse();
      contract_value = product_info->contract_value();
    }
  }

  std::unordered_map<int32_t, SignStats> sstats;
  double pos = std::numeric_limits<double>::quiet_NaN();
  std::vector<double> bals =
      std::vector<double>(NUM_BALTYPE, std::numeric_limits<double>::quiet_NaN());
  std::unordered_set<int64_t> filled_order_ids;

  void FlushRhsFill() {
    auto& sstat_buy = sstats[1];
    sstat_buy.lhs_makerq += sstat_buy.rhs_makerq;
    sstat_buy.rhs_makerq = 0;
    sstat_buy.lhs_takerq += sstat_buy.rhs_takerq;
    sstat_buy.rhs_takerq = 0;
    sstat_buy.lhs_makerpq += sstat_buy.rhs_makerpq;
    sstat_buy.rhs_makerpq = 0;
    sstat_buy.lhs_takerpq += sstat_buy.rhs_takerpq;
    sstat_buy.rhs_takerpq = 0;
    auto& sstat_sell = sstats[-1];
    sstat_sell.lhs_makerq += sstat_sell.rhs_makerq;
    sstat_sell.rhs_makerq = 0;
    sstat_sell.lhs_takerq += sstat_sell.rhs_takerq;
    sstat_sell.rhs_takerq = 0;
    sstat_sell.lhs_makerpq += sstat_sell.rhs_makerpq;
    sstat_sell.rhs_makerpq = 0;
    sstat_sell.lhs_takerpq += sstat_sell.rhs_takerpq;
    sstat_sell.rhs_takerpq = 0;
  }

  void UpdateOrderEvent(const coin::proto::OrderEvent& oe) {
    int32_t sign = coin2::exchange::base::order::GetSign(oe.order_side(), false);
    auto& sstat = sstats[sign];
    if (oe.type() == coin::proto::OrderEvent::ORDER_SUBMITTED) {
      sstat.req += 1;
    }
    if (oe.type() == coin::proto::OrderEvent::ORDER_ACCEPTED) {
      sstat.subpq += oe.order_price() * oe.order_qty();
      sstat.subq += oe.order_qty();
    }
    if (oe.type() == coin::proto::OrderEvent::ORDER_FILLED) {
      if (filled_order_ids.count(oe.proc_order_id()) == 0) {
        sstat.fill_subamt += is_inverse ? oe.order_qty() : (oe.order_price() * oe.order_qty());
        filled_order_ids.insert(oe.proc_order_id());
      }
      sstat.fillpq += oe.fill_price() * oe.fill_qty();
      sstat.fillq += oe.fill_qty();

      if (oe.fill_type() == coin::proto::FillType::MAKER_FILL_TYPE) {
        sstat.rhs_makerq += oe.fill_qty();
        sstat.rhs_makerpq += oe.fill_price() * oe.fill_qty();
      } else if (oe.fill_type() == coin::proto::FillType::TAKER_FILL_TYPE) {
        sstat.rhs_takerq += oe.fill_qty();
        sstat.rhs_takerpq += oe.fill_price() * oe.fill_qty();
      } else if (oe.post_only()) {
        sstat.rhs_makerq += oe.fill_qty();
        sstat.rhs_makerpq += oe.fill_price() * oe.fill_qty();
      } else {
        sstat.rhs_takerq += oe.fill_qty();
        sstat.rhs_takerpq += oe.fill_price() * oe.fill_qty();
      }
    }
  }
  void clear() {
    sstats.clear();
  }
};

struct PnlCurrencyStat {
  std::string currency;
  double bal;

  explicit PnlCurrencyStat(const std::string& currency_) {
    currency = currency_;
    clear();
  }

  void UpdatePnlBalance(const coin::proto::CurrencyBalance& cb) {
    bal = cb.total();
  }

  void clear() {
    bal = 0.0;
  }
};

DEFINE_ENUM_WITH_STRING_CONVERSIONS(
    RecordType,
    (SUB_REQ_CNT)
    (SUB_FILLED_AMT)
    (SUB_BUY_QTY)
    (SUB_BUY_VWAP)
    (SUB_SELL_QTY)
    (SUB_SELL_VWAP)
    (IS_INVERSE)
    (CONTRACT_VALUE)
    (MAKER_FEE_RATE)
    (TAKER_FEE_RATE)
    (FILL_BUY_QTY)
    (FILL_BUY_VWAP)
    (FILL_SELL_QTY)
    (FILL_SELL_VWAP)
    (LHS_MAKER_BUY_QTY)
    (LHS_MAKER_BUY_VWAP)
    (LHS_TAKER_BUY_QTY)
    (LHS_TAKER_BUY_VWAP)
    (LHS_MAKER_SELL_QTY)
    (LHS_MAKER_SELL_VWAP)
    (LHS_TAKER_SELL_QTY)
    (LHS_TAKER_SELL_VWAP)
    (RHS_MAKER_BUY_QTY)
    (RHS_MAKER_BUY_VWAP)
    (RHS_TAKER_BUY_QTY)
    (RHS_TAKER_BUY_VWAP)
    (RHS_MAKER_SELL_QTY)
    (RHS_MAKER_SELL_VWAP)
    (RHS_TAKER_SELL_QTY)
    (RHS_TAKER_SELL_VWAP)
    (POS)
    (BAL)
    (RESERVE)
    (NUM_RECORDTYPE))

struct StratStatRecord {
  explicit StratStatRecord(size_t symbol_size, size_t pnl_currency_size)
      : rt_syms(NUM_RECORDTYPE, std::vector<double>(symbol_size)),
      pnl_currency_bals(pnl_currency_size) {
  }
  std::vector<std::vector<double>> rt_syms;
  std::vector<double> pnl_currency_bals;

  void UpdateBySymStat(size_t index, const SymStat& sym_stat) {
    rt_syms[SUB_REQ_CNT][index] = 0;
    rt_syms[SUB_FILLED_AMT][index] = 0;
    if (sym_stat.sstats.count(1) > 0){
      const auto& stat_buy = sym_stat.sstats.at(1);
      rt_syms[SUB_REQ_CNT][index] += stat_buy.req;
      rt_syms[SUB_FILLED_AMT][index] += stat_buy.fill_subamt;
      rt_syms[SUB_BUY_QTY][index] = stat_buy.subq;
      rt_syms[SUB_BUY_VWAP][index] = (stat_buy.subq == 0) ? 0 : stat_buy.subpq / stat_buy.subq;
      rt_syms[LHS_MAKER_BUY_QTY][index] = stat_buy.lhs_makerq;
      rt_syms[LHS_MAKER_BUY_VWAP][index] = (stat_buy.lhs_makerq == 0) ? 0 : stat_buy.lhs_makerpq / stat_buy.lhs_makerq;
      rt_syms[LHS_TAKER_BUY_QTY][index] = stat_buy.lhs_takerq;
      rt_syms[LHS_TAKER_BUY_VWAP][index] = (stat_buy.lhs_takerq == 0) ? 0 : stat_buy.lhs_takerpq / stat_buy.lhs_takerq;
      rt_syms[RHS_MAKER_BUY_QTY][index] = stat_buy.rhs_makerq;
      rt_syms[RHS_MAKER_BUY_VWAP][index] = (stat_buy.rhs_makerq == 0) ? 0 : stat_buy.rhs_makerpq / stat_buy.rhs_makerq;
      rt_syms[RHS_TAKER_BUY_QTY][index] = stat_buy.rhs_takerq;
      rt_syms[RHS_TAKER_BUY_VWAP][index] = (stat_buy.rhs_takerq == 0) ? 0 : stat_buy.rhs_takerpq / stat_buy.rhs_takerq;
      rt_syms[FILL_BUY_QTY][index] = stat_buy.fillq;
      rt_syms[FILL_BUY_VWAP][index] = (stat_buy.fillq == 0) ? 0 : stat_buy.fillpq / stat_buy.fillq;
    }
    if (sym_stat.sstats.count(-1) > 0){
      const auto& stat_sell = sym_stat.sstats.at(-1);
      rt_syms[SUB_REQ_CNT][index] += stat_sell.req;
      rt_syms[SUB_FILLED_AMT][index] += stat_sell.fill_subamt;
      rt_syms[SUB_SELL_QTY][index] = stat_sell.subq;
      rt_syms[SUB_SELL_VWAP][index] = (stat_sell.subq == 0) ? 0 : stat_sell.subpq / stat_sell.subq;
      rt_syms[LHS_MAKER_SELL_QTY][index] = stat_sell.lhs_makerq;
      rt_syms[LHS_MAKER_SELL_VWAP][index] = (stat_sell.lhs_makerq == 0) ? 0 : stat_sell.lhs_makerpq / stat_sell.lhs_makerq;
      rt_syms[LHS_TAKER_SELL_QTY][index] = stat_sell.lhs_takerq;
      rt_syms[LHS_TAKER_SELL_VWAP][index] = (stat_sell.lhs_takerq == 0) ? 0 : stat_sell.lhs_takerpq / stat_sell.lhs_takerq;
      rt_syms[RHS_MAKER_SELL_QTY][index] = stat_sell.rhs_makerq;
      rt_syms[RHS_MAKER_SELL_VWAP][index] = (stat_sell.rhs_makerq == 0) ? 0 : stat_sell.rhs_makerpq / stat_sell.rhs_makerq;
      rt_syms[RHS_TAKER_SELL_QTY][index] = stat_sell.rhs_takerq;
      rt_syms[RHS_TAKER_SELL_VWAP][index] = (stat_sell.rhs_takerq == 0) ? 0 : stat_sell.rhs_takerpq / stat_sell.rhs_takerq;
      rt_syms[FILL_SELL_QTY][index] = stat_sell.fillq;
      rt_syms[FILL_SELL_VWAP][index] = (stat_sell.fillq == 0) ? 0 : stat_sell.fillpq / stat_sell.fillq;
    }
    rt_syms[IS_INVERSE][index] = sym_stat.is_inverse;
    rt_syms[CONTRACT_VALUE][index] = sym_stat.contract_value;
    rt_syms[MAKER_FEE_RATE][index] = sym_stat.maker_fee_rate;
    rt_syms[TAKER_FEE_RATE][index] = sym_stat.taker_fee_rate;
    rt_syms[POS][index] = sym_stat.pos;
    rt_syms[BAL][index] = sym_stat.bals[BALTYPE_BALANCE];
    rt_syms[RESERVE][index] = sym_stat.bals[BALTYPE_RESERVE];
  }

  void UpdateByPnlCurrencyStat(size_t index, const PnlCurrencyStat& pnl_currency_stat) {
    pnl_currency_bals[index] = pnl_currency_stat.bal;
  }
};

struct StratStat {
  std::string strategy_name;
  std::unique_ptr<HighFive::File> file;
  int64_t last_ltime = 0L;
  std::unordered_map<std::string, int64_t> sindex;
  std::unordered_map<std::string, int64_t> pnl_currency_index;
  std::vector<std::string> symbol_strs;
  std::vector<SymStat> sstats;
  std::vector<PnlCurrencyStat> pnl_currency_stats;
  std::vector<int64_t> ltimes;
  std::vector<StratStatRecord> ssrecords;

  StratStat(
      const std::experimental::filesystem::path& out_h5_root,
      const std::string& date,
      const std::string& strategy_name,
      int64_t begin_ts)
      : strategy_name(strategy_name) {
    last_ltime = begin_ts;
    auto out_dir = out_h5_root / date;
    std::experimental::filesystem::create_directories(out_dir);
    auto out_file = out_dir / fmt::format("{}.h5", strategy_name);
    file.reset(new HighFive::File(
        out_file, HighFive::File::ReadWrite | HighFive::File::Create | HighFive::File::Truncate));
  }

  virtual ~StratStat() {
    WriteRowsOnFile();
  }

  void WriteRowsOnFile() {
    if (last_ltime > 0) {
      WriteRowOnMemory(last_ltime);
      last_ltime = 0;
      std::vector<size_t> dims = {ltimes.size(), sindex.size()};
      file->createDataSet("/strategy_name", HighFive::FixedLenStringArray<100>{strategy_name});
      file->createDataSet("/market", HighFive::FixedLenStringArray<100>{MarketType_Name(market)});
      file->createDataSet("/market_type", std::vector<int>{static_cast<int>(market)});
      file->createDataSet("/exchange", HighFive::FixedLenStringArray<100>{ExchangeType_Name(exchange)});
      file->createDataSet("/exchange_type", std::vector<int>{static_cast<int>(exchange)});
      CHECK_EQ(ltimes.size(), ssrecords.size());
      auto timesdata = file->createDataSet<double>("/times", HighFive::DataSpace(dims[0]));
      timesdata.write_raw(ltimes.data());

      CHECK_EQ(symbol_strs.size(), dims[1]);
      HighFive::FixedLenStringArray<100> symstr_array;
      for (const auto& symbol_str : symbol_strs) {
        symstr_array.push_back(symbol_str);
      }
      file->createDataSet("/symbols", symstr_array);

      CHECK_EQ(pnl_currency_index.size(), pnl_currency_stats.size());
      HighFive::FixedLenStringArray<100> pnl_currency_str_array;
      for (const auto& stat: pnl_currency_stats) {
        pnl_currency_str_array.push_back(stat.currency);
      }
      file->createDataSet("/pnl_currencies", pnl_currency_str_array);

      for (size_t irec = 0; irec < NUM_RECORDTYPE; ++irec) {
        auto recstr = ToString(static_cast<RecordType>(irec));
        std::vector<double> xmat_flat;
        xmat_flat.reserve(dims[0] * dims[1]);
        for (size_t it = 0; it < ltimes.size(); ++it) {
          for (size_t is = 0; is < sindex.size(); ++is) {
            xmat_flat.push_back(0);
            if (is < ssrecords[it].rt_syms[irec].size()) {
              xmat_flat.back() = ssrecords[it].rt_syms[irec][is];
            }
          }
        }
        CHECK_EQ(xmat_flat.size(), dims[0] * dims[1]);
        auto recdata = file->createDataSet<double>(
            fmt::format("/{}", recstr), HighFive::DataSpace(dims));
        recdata.write_raw(xmat_flat.data());
      }

      {
        std::vector<size_t> pnl_dims = {ltimes.size(), pnl_currency_index.size()};
        std::vector<double> xmat_flat;
        xmat_flat.reserve(pnl_dims[0] * pnl_dims[1]);
        for (size_t it = 0; it < ltimes.size(); ++it) {
          for (size_t is = 0; is < pnl_currency_index.size(); ++is) {
            xmat_flat.push_back(0);
            if (is < ssrecords[it].pnl_currency_bals.size()) {
              xmat_flat.back() = ssrecords[it].pnl_currency_bals[is];
            }
          }
        }
        CHECK_EQ(xmat_flat.size(), pnl_dims[0] * pnl_dims[1]);
        auto recdata = file->createDataSet<double>("/PNL", HighFive::DataSpace(pnl_dims));
        recdata.write_raw(xmat_flat.data());
      }
    }
  }

  void WriteRowOnMemory(int64_t ltime) {
    ltimes.push_back(ltime);
    ssrecords.push_back(StratStatRecord(sindex.size(), pnl_currency_index.size()));
    for (size_t i = 0; i < sindex.size(); ++i) {
      ssrecords.back().UpdateBySymStat(i, sstats[i]);
      sstats[i].clear();
    }
    for (size_t i = 0; i < pnl_currency_index.size(); ++i) {
      ssrecords.back().UpdateByPnlCurrencyStat(i, pnl_currency_stats[i]);
      pnl_currency_stats[i].clear();
    }
    SPDLOG_INFO_EVERY_NS(600e9, ltime, "{}", ltime);
  }

  std::unordered_map<std::string, std::string> sym2ccy;
  std::unordered_map<std::string, std::string> ccy2sym;

  MarketType market;
  ExchangeType exchange;
  coin2::exchange::base::symbology::ProductEncyclopedia enc;

  SymStat& GetSymStat(const std::string& norm_symbol, bool null_product = false) {
    CHECK(!norm_symbol.empty());
    if (sindex.count(norm_symbol) == 0) {
      sindex[norm_symbol] = sindex.size();
      symbol_strs.push_back(norm_symbol);
      if (null_product) {
        sstats.push_back(SymStat(nullptr));
      } else {
        auto product = CreateProductFromNormString(market, exchange, norm_symbol, last_ltime);
        sstats.push_back(SymStat(&(enc.holder(*product).product_info())));
      }
    }
    if (sym2ccy.count(norm_symbol) == 0) {
      std::string ccy = Split(norm_symbol, "-")[0];
      sym2ccy[norm_symbol] = ccy;
      ccy2sym[ccy] = norm_symbol;
    }
    return sstats[sindex.at(norm_symbol)];
  }

  PnlCurrencyStat& GetPnlCurrencyStat(const std::string& currency) {
    CHECK(!currency.empty());
    if (pnl_currency_index.count(currency) == 0) {
      pnl_currency_index[currency] = pnl_currency_index.size();
      pnl_currency_stats.push_back(PnlCurrencyStat(currency));
    }
    return pnl_currency_stats[pnl_currency_index.at(currency)];
  }

  SymStat* GetSymStatFromCcy(const std::string& ccy) {
    if (ccy2sym.count(ccy) == 0) return nullptr;
    return &GetSymStat(ccy2sym.at(ccy));
  }

  void UpdateAccountRequest(const coin::proto::AccountRequestProto& acc_request) {
    MarketType_Parse(acc_request.market_type(), &market);
    ExchangeType_Parse(acc_request.exchange(), &exchange);
  }

  void UpdatePosition(int64_t timestamp, const coin::proto::AccountPosition& position) {
    if (position.exchange() == "Otc") {
      for (const auto& each_pos : position.each_position()) {
        auto& sym_stat = GetSymStat(each_pos.symbol(), true);
        sym_stat.pos = each_pos.net_position();
        sym_stat.FlushRhsFill();
      }
      return;
    }
    UpdateTimeOnly(timestamp);
    for (const auto& each_pos : position.each_position()) {
      CHECK(!each_pos.symbol().empty()) << position.DebugString();
      if (each_pos.net_position() == 0 && sindex.count(each_pos.symbol()) == 0) continue;
      GetSymStat(each_pos.symbol()).pos = each_pos.net_position();
      GetSymStat(each_pos.symbol()).FlushRhsFill();
    }
  }

  void UpdateBalance(
      int64_t timestamp,
      const coin::proto::AccountBalance& balance,
      BalanceType baltype) {
    if (balance.exchange() == "Otc") return;
    for (const auto& each_balance : balance.each_balance()) {
      auto* sstat_ptr = GetSymStatFromCcy(each_balance.currency());
      if (sstat_ptr) {
        sstat_ptr->bals[baltype] = each_balance.total();
        sstat_ptr->FlushRhsFill();
      }
    }
  }

  void UpdatePnlBalance(
    int64_t timestamp,
    const coin::proto::CurrencyBalance& balance) {
    GetPnlCurrencyStat(balance.currency()).UpdatePnlBalance(balance);
  }

  void UpdateOrderEvent(int64_t timestamp, const coin::proto::OrderEvent& oe) {
    if (oe.symbol().empty()) return;
    UpdateTimeOnly(timestamp);
    GetSymStat(oe.symbol()).UpdateOrderEvent(oe);
  }

  void UpdateTimeOnly(int64_t timestamp) {
    for (; last_ltime + kUnitSizeNs <= timestamp; last_ltime += kUnitSizeNs) {
      WriteRowOnMemory(last_ltime);
    }
  }
};


int main(int argc, char* argv[]) {
  ::std::set_terminate([] {
    ::backward::StackTrace st;
    st.load_here(32);
    ::backward::Printer p;
    p.object = true;
    p.color_mode = ::backward::ColorMode::always;
    p.address = true;
    p.print(st, stderr);
    ::std::abort();
  });
  cxxopts::Options opt("stratlog_h5_dumper", "stratlog h5 dumper");
  opt.add_options()(
      "out_h5_root",
      "out h5 root",
      cxxopts::value<std::experimental::filesystem::path>());
  coin2::base::config::AddDriverOptions(&opt);
  coin2::base::config::AddIntervalOptions(&opt);
  coin2::base::config::AddLogArchiveOptions(&opt);
  auto res = opt.parse(argc, argv);
  coin2::app::DriverConfig app;
  coin2::base::config::ParseIntervalOptions(
      res,
      app.mutable_feed()->mutable_common()->mutable_archive()->mutable_interval());
  SPDLOG_INFO("{}", app.DebugString());

  coin::proto::StrategyRequestProto req;
  req.set_strategy_name(res["log_strategy_name"].as<std::string>());

  const size_t BUF_SIZE = 100 * 1024;
  auto begin_end = coin2::base::config::GetEnclosingInterval(app);

  auto log_type_root = res["log_root"].as<std::experimental::filesystem::path>();
  log_type_root.append(res["log_type"].as<std::string>());

  SPDLOG_INFO("[{}, {}]", begin_end.first, begin_end.second);

  coin2::exchange::base::strategy_util::StrategyLogReader reader(
      req,
      log_type_root,
      res["log_machine"].as<std::string>(),
      begin_end.first,
      begin_end.second,
      BUF_SIZE);

  coin::proto::StrategyLog slproto;

  StratStat sstat(
      res["out_h5_root"].as<std::experimental::filesystem::path>(),
      res["start"].as<std::string>(),
      res["log_strategy_name"].as<std::string>(),
      begin_end.first);

  int64_t timestamp = 0L;

  std::vector<std::pair<int64_t, coin::proto::StrategyLog>> rows;
  while (reader.Read(&slproto, &timestamp)) {
    if (slproto.og_log().type() != coin::proto::OrderGatewayLog::BALANCE &&
        slproto.og_log().type() != coin::proto::OrderGatewayLog::POSITION &&
        slproto.og_log().type() != coin::proto::OrderGatewayLog::ORDER_EVENT &&
        slproto.type() != coin::proto::StrategyLog::PNL_BALANCE &&
        slproto.type() != coin::proto::StrategyLog::RESERVE) {
      continue;
    }
    rows.push_back(std::make_pair(timestamp, slproto));
  }
  std::sort(
      rows.begin(), rows.end(),
      [](auto &left, auto &right) {return left.first < right.first;});

  for (const auto& [timestamp, slproto] : rows) {
    if (slproto.account_request().exchange() == "Otc") continue;
    if (slproto.og_log().has_account_request()) {
      sstat.UpdateAccountRequest(slproto.og_log().account_request());
    }
    switch (slproto.og_log().type()) {
      case coin::proto::OrderGatewayLog::POSITION:
        sstat.UpdatePosition(timestamp, slproto.og_log().position());
        break;
      case coin::proto::OrderGatewayLog::ORDER_EVENT:
        sstat.UpdateOrderEvent(timestamp, slproto.og_log().event());
        break;
      case coin::proto::OrderGatewayLog::BALANCE:
        sstat.UpdateBalance(timestamp, slproto.og_log().balance(), BALTYPE_BALANCE);
        break;
      default:;
    }
    if (slproto.type() == coin::proto::StrategyLog::RESERVE) {
      sstat.UpdateBalance(timestamp, slproto.reserve(), BALTYPE_RESERVE);
    } else if (slproto.type() == coin::proto::StrategyLog::PNL_BALANCE) {
      sstat.UpdatePnlBalance(timestamp, slproto.pnl_balance());
    }
  }
  sstat.UpdateTimeOnly(begin_end.second - 1);
  return 0;
}
